﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Test
{
    internal static class Comparer
    {
        public static bool Compare(object a, object b)
        {
            if (a == null) return (b == null);
            if (b == null) return false;

            bool? c = ComparePrimary(a, b);

            if (c.HasValue) return c.Value;

            IEnumerable ae = a as IEnumerable;
            if (ae != null) return CompareEnumerable(b, ae);

            return CompareByReflection(a, b);
        }

        private static bool? ComparePrimary(object a, object b)
        {
            return Compare<string>(a, b) ??
                        Compare<Int32>(a, b) ??
                        Compare<Int16>(a, b) ??
                        Compare<Int64>(a, b) ??
                        Compare<Int32?>(a, b) ??
                        Compare<Int16?>(a, b) ??
                        Compare<Int64?>(a, b) ??
                        Compare<UInt32>(a, b) ??
                        Compare<UInt16>(a, b) ??
                        Compare<UInt64>(a, b) ??
                        Compare<UInt32?>(a, b) ??
                        Compare<UInt16?>(a, b) ??
                        Compare<UInt64?>(a, b) ??
                        Compare<DateTime>(a, b) ??
                        Compare<decimal>(a, b) ??
                        Compare<float>(a, b) ??
                        Compare<bool>(a, b) ??
                        Compare<TimeSpan>(a, b) ??
                        Compare<Guid>(a, b) ??
                        Compare<DateTime?>(a, b) ??
                        Compare<decimal?>(a, b) ??
                        Compare<float?>(a, b) ??
                        Compare<bool?>(a, b) ??
                        Compare<TimeSpan?>(a, b) ??
                        Compare<Guid?>(a, b);
        }

        private static bool CompareByReflection(object a, object b)
        {
            Type ta = a.GetType();
            Type tb = b.GetType();

            if (ta.IsEnum)
            {
                object a2 = Convert.ChangeType(a, Enum.GetUnderlyingType(ta), null);
                object b2 = Convert.ChangeType(b, Enum.GetUnderlyingType(tb), null);
                return Compare(a2, b2);
            }

            var lkb = tb.GetProperties().Where(pb => pb.CanRead).ToDictionary(pb => pb.Name);

            foreach (var pa in ta.GetProperties())
            {
                if (!lkb.ContainsKey(pa.Name)) continue;

                var pb = lkb[pa.Name];

                object ai = pa.GetValue(a, null);
                object bi = pb.GetValue(b, null);

                if (!Compare(ai, bi)) return false;
            }

            return true;
        }

        private static bool CompareEnumerable(object b, IEnumerable ae)
        {
            IEnumerable be = b as IEnumerable;
            if (be == null) return false;
            IEnumerator bet = be.GetEnumerator();
            using (bet as IDisposable)
            {
                foreach (object ai in ae)
                {
                    if (!bet.MoveNext()) return false;
                    object bi = bet.Current;
                    if (!Compare(ai, bi)) return false;
                }
                return !bet.MoveNext();//ae has finished, has be
            }
        }
        private static bool? Compare<T>(object a, object b)
        {
            if (a is DateTime && b is DateTime)
            {
                if (((DateTime)a).Kind != ((DateTime)b).Kind) return false;
            }

            if (a is T) return a.Equals(b);
            return null;
        }

    }
}
